#include "ThreadSimpleDemoOfCpp.h"

#include <sstream>
#include <iostream>
#include <thread>
using namespace std;

void simpleFunction1();
void simpleFunction2(int number, string name);

void threadSimpleDemoOfCpp()
{
  // 在 C++ 11 标准之前 C++ 语言没有对并发编程提供语言级别的支持。还是需要通过 C 语言的线程库完成，这导致在可移植方面表现并不是很好。
  // C++ 11 提供了线程类叫做 std::thread，它提供了有别于 C 语言代码库的操作方式，基于类的操作，在使用上简单很多，不必记忆太多方法。使用类成员来完成操作，在代码提示帮助下更容易使用。

  // 我们首先来了解一下这个类提供的一些常用 API：

  // 构造函数
  // 构造函数 1：默认构造函，构造一个线程对象，在这个线程中不执行任何处理动作
  // thread() noexcept;
  // 构造函数 2：移动构造函数，将 other 的线程所有权转移给新的thread 对象。之后 other 不再表示执行线程。
  // thread(thread&& other) noexcept;
  // 构造函数 3：创建线程对象，并在该线程中执行函数 f 中的业务逻辑，args 是要传递给函数 f 的参数
  //  * 任务函数f的可选类型有很多，具体如下：
  //    - 普通函数，类成员函数，匿名函数，仿函数（这些都是可调用对象类型）
  //    - 可以是可调用对象包装器类型，也可以是使用绑定器绑定之后得到的类型（仿函数）
  /*
     template<class Function, class... Args>
     explicit thread(Function&& f, Args&&... args);
   */
  // 构造函数 4：使用=delete显示删除拷贝构造, 不允许线程对象之间的拷贝
  // thread(const thread&) = delete;

  // 公共成员函数
  // 1. get_id()
  // 应用程序启动之后默认只有一个线程，这个线程一般称之为主线程或父线程，通过线程类创建出的线程一般称之为子线程，
  // 每个被创建出的线程实例都对应一个线程 ID，这个 ID 是唯一的，可以通过这个 ID 来区分和识别各个已经存在的线程实例，这个获取线程 ID 的函数叫做 get_id()，
  // 函数原型如下：
  // std::thread::id get_id() const noexcept;

  // 示例程序如下：
  // this_thread 当前线程的相关操作
  cout << "主线程的线程 ID: " << this_thread::get_id() << endl;

  // 子线程对象 simpleThread1 中的任务函数 simpleFunction1()，没有参数，因此在 '线程构造函数' 中就无需指定了。
  thread simpleThread1(simpleFunction1);
  // 创建了子线程对象 simpleThread2：
  //  * simpleFunction2：是原型为 void simpleFunction2(int number, string name) 的函数，在后面的代码中定义了。
  //                     它是一个回调函数，会在这个子线程中运行，程序猿只需要实现即可，线程启动之后就会执行这个任务函数；
  //  * [520, "lw"]：是传递给 simpleFunction2 函数调用时所需的参数；
  //  * 线程类的 '构造函数 3' 是一个变参模板函数，因此无需担心线程任务函数的参数个数和类型等问题；
  //  * 任务函数 simpleFunction2() 一般返回值指定为 void，因为子线程在调用这个函数的时候不会处理其返回值。
  thread simpleThread2(simpleFunction2, 520, "lw");

  // 通过线程对象调用 get_id() 就可以知道这个子线程的线程 ID 了：simpleThread1.get_id() 和 simpleThread2.get_id()。
  // 这里使用 stringstream 来拼接字符串，因为:
  //  * 直接 cout << 分段输出的话，输出流使用同一个会导致其他线程输出是的文字顺序被打乱。
  //  * get_id() 返回的时一个 class 类型，不能直接参与到 string 的拼接中 + 运算符没有针对这种类型的重载，导致类型不能转换。但是 get_id() 返回的类重写了 << 运算符。所以可以这样做。
  // get_id() 在 posix 模式下其本质就是使用了 pthread_self 返回了 pthread_t 类型。而线程也是用 pthread 完成的。所以语 C 库无异，只是使用方式不同。
  // get_id() 在 win32 模式下应该有所不同，目前可以猜测 多线程大多都是 posix 。linux应该都是，windows则可能又一套自己的实现方式但 C++ 11标准应该也只是提出了统一的封装标准即接口而已。
  // 至于其他的 windows api 实现的线程目前没有了解
  stringstream msgStream;
  msgStream << "simpleThread1 的 ID：" << simpleThread1.get_id() << endl;
  cout << msgStream.str();
  msgStream.str(""); // 清空字符串流
  msgStream << "simpleThread2 的 ID：" << simpleThread2.get_id() << endl;
  cout << msgStream.str();

  // 在上面的示例程序中有一个 bug，在主线程中依次创建出两个子线程，打印两个子线程的线程 ID，最后主线程执行完毕就退出了（主线程就是执行 main() 函数的那个线程）。
  // 默认情况下，主线程销毁时会将与其关联的两个子线程也一并销毁，但是这时有可能子线程中的任务还没有执行完毕，最后也就得不到我们想要的结果了。

  // 当启动了一个线程（创建了一个 thread 对象）之后，在这个线程结束的时候（ std::terminate() ），我们如何去回收线程所使用的资源呢？ thread 库给我们两种选择：
  //  * 加入式（join()）
  //  * 分离式（detach()）
  // 另外，我们必须要在线程对象销毁之前在二者之间作出选择，否则程序运行期间就会有 bug 产生。

  // 2. join()
  // join() 字面意思是连接一个线程，意味着主动地等待线程的终止（线程阻塞）。在某个线程中通过子线程对象调用 join() 函数，调用这个函数的线程被阻塞，
  // 但是子线程对象中的任务函数会继续执行，当任务执行完毕之后 join() 会清理当前子线程中的相关资源然后返回，同时，调用该函数的线程解除阻塞继续向下执行。
  // 再次强调，我们一定要搞清楚这个函数阻塞的是哪一个线程，函数在哪个线程中被执行，那么函数就阻塞哪个线程。
  //
  // 该函数的函数原型如下：
  //  - void join();
  //
  // 有了这样一个线程阻塞函数之后，就可以解决在上面测试程序中的 bug 了，如果要阻塞主线程的执行，只需要在主线程中通过子线程对象调用这个方法即可，
  // 当调用这个方法的子线程对象中的任务函数执行完毕之后，主线程的阻塞也就随之解除了。
  // 修改只需增加如下代码：

  simpleThread1.join();
  simpleThread2.join();

  // 当主线程运行到上面的 join() 函数时，会根据子线程对象 simpleThread1 的任务函数 simpleFunction1() 的执行情况，主线程会做如下处理：
  //  * 如果任务函数 simpleFunction1() 还没执行完毕，主线程阻塞，直到任务执行完毕，主线程解除阻塞，继续向下运行
  //  * 如果任务函数 simpleFunction1() 已经执行完毕，主线程不会阻塞，继续向下运行
  // simpleThread2 也会依此类推。

  // 为了更好的理解 join() 的使用，再来给大家举一个例子，场景如下：
  // 程序中一共有三个线程，其中两个子线程负责分段下载同一个文件，下载完毕之后，由主线程对这个文件进行下一步处理，那么示例程序就应该这么写：
  /*
  void downloadFileDemo();
  downloadFileDemo();
   */

  // 3. detach()
  // detach() 函数的作用是进行线程分离，分离主线程和创建出的子线程。
  // 在线程分离之后，主线程退出也会一并销毁创建出的所有子线程，在主线程退出之前，它可以脱离主线程继续独立的运行，任务执行完毕之后，这个子线程会自动释放自己占用的系统资源。
  // （其实就是孩子翅膀硬了，和家里断绝关系，自己外出闯荡了，如果家里被诛九族还是会受牵连）。
  //
  // summer: C 库中的 pthread_detach 也只会将子线程从主线程分离，不在需要主线程来维护资源回收，而是子线程自动回收，同样主线程也无法拿到子线程的返回。主线程退出时会回收堆栈资源同样子线程也会
  //         受到影响，但是 C 库可以再分离后使用 pthread_eixt 来退出主线程，这种推出并不会影响子线程即便是运行程序 eixt(0) 退出也不会影响。此时子线程算是彻底脱离。
  //         但没有调用 pthread_eixt 的线程退出后会回收对战资源，也会导致分离的线程杯牵连。这一点于 C++ 一样，所以 C++ 中会受牵连可能因为没有 pthread_eixt 函数。但是我们也很容易能理解这个
  //         设定，因为 C++ 的标准没有明确线程的实现，只是相当于线程接口，再 winGW 下线程有两种模式：posix 和 win32，其中 posix 就是 linux 下 C 库的线程模式，从开放的 thread 头文件定义，
  //         也能看到关于使用了 pthread 头文件库的痕迹(目前是在 winGW 编译器提供的头文件定义里)。所以两者有相似的是可以理解的。
  //
  // 该函数函数原型如下：
  //  - void detach();
  //
  // 线程分离函数没有参数也没有返回值，只需要在线程成功之后，通过线程对象调用该函数即可，继续将上面的测试程序修改一下：
  /*
  simpleThread1.detach();
  simpleThread2.detach();
  // 让主线程休眠, 等待子线程执行完毕
  this_thread::sleep_for(chrono::seconds(5));
   */
  // 注意事项：线程分离函数 detach() 不会阻塞线程，子线程和主线程分离之后，在主线程中就不能再对这个子线程做任何控制了，
  // 比如：通过 join() 阻塞主线程等待子线程中的任务执行完毕，或者调用 get_id() 获取子线程的线程 ID。有利就有弊，鱼和熊掌不可兼得，建议使用 join()。

  // 4. joinable()
  // joinable() 函数用于判断主线程和子线程是否处理关联（连接）状态，一般情况下，二者之间的关系处于关联状态
  //
  // 该函数函数原型如下：
  //
  //  - bool joinable() const noexcept;
  //
  // 该函数返回一个布尔类型：
  // 返回值为 true：主线程和子线程之间有关联（连接）关系
  // 返回值为 false：主线程和子线程之间没有关联（连接）关系
  //
  // 示例代码如下：
  thread joinableThread;
  cout << "joinableThread 创建时，连接状态：" << joinableThread.joinable() << endl;
  // 尝试去 join 或 detach 一个连接状态为 false 的线程都是非法的，会报错。
  // joinableThread.join();

  thread joinableThread1([]() {});
  cout << "joinableThread1 创建时，连接状态：" << joinableThread1.joinable() << endl;
  joinableThread1.join();
  cout << "joinableThread1 执行 join() 后，连接状态：" << joinableThread1.joinable() << endl;

  thread joinableThread2([]() {});
  cout << "joinableThread2 创建时，连接状态：" << joinableThread2.joinable() << endl;
  joinableThread2.detach();
  cout << "joinableThread2 执行 detach() 后，连接状态：" << joinableThread2.joinable() << endl;

  // 基于示例代码打印的结果可以得到以下结论：
  //  * 在创建的子线程对象的时候，如果没有指定任务函数，那么子线程不会启动，主线程和这个子线程也不会进行连接；
  //  * 在创建的子线程对象的时候，如果指定了任务函数，子线程启动并执行任务，主线程和这个子线程自动连接成功；
  //  * 子线程调用了 detach() 函数之后，父子线程分离，同时二者的连接断开，调用 joinable() 返回 false；
  //  * 在子线程调用了 join() 函数，子线程中的任务函数继续执行，直到任务处理完毕，这时 join() 会清理（回收）当前子线程的相关资源，
  //    所以这个子线程和主线程的连接也就断开了，因此，调用 join() 之后再调用 joinable() 会返回 false。

  // 5. operator=
  // 线程中的资源是不能被复制的，因此通过 = 操作符进行赋值操作最终并不会得到两个完全相同的对象。
  //
  // 该操作符重载函数原型如下：
  //
  // move (1)
  //  - thread& operator= (thread&& other) noexcept;
  // copy [deleted] (2)
  //  - thread& operator= (const other&) = delete;
  //
  // 通过以上 = 操作符的重载声明可以得知：
  //  * 如果 other 是一个右值，会进行资源所有权的转移
  //  * 如果 other 不是右值，禁止拷贝，该函数被显示删除（=delete），不可用

  // 6. 静态函数
  // thread 线程类还提供了一个静态方法，用于获取当前计算机的 CPU 核心数，根据这个结果在程序中创建出数量相等的线程，
  // 每个线程独自占有一个 CPU 核心，这些线程就不用分时复用 CPU 时间片，此时程序的并发效率是最高的。
  //
  // 该函数函数原型如下：
  //  - static unsigned hardware_concurrency() noexcept;
  //
  // 示例代码如下：
  cout << "这个电脑的 CPU 核心数有" << thread::hardware_concurrency() << "个" << endl;

  // C 线程库
  // C 语言提供的线程库不论在 window 还是 Linux 操作系统中都是可以使用的，看明白了这些 C 语言中的线程函数之后会发现它和上面的 C++ 线程类使用很类似（其实就是基于面向对象的思想进行了封装），
  // 但 C++ 的线程类用起来更简单一些。而 C 库性能更好，被更多标准支持。
}

void simpleFunction1()
{
  for (int index = 0; index < 10; index++)
  {
    stringstream msgStream;
    msgStream << "simpleFunction1 线程：id=" << this_thread::get_id() << "; numbber=" << index << endl;
    cout << msgStream.str();
  }
}

void simpleFunction2(int number, string name)
{
  for (int index = 0; index < 10; ++index)
  {
    stringstream msgStream;
    msgStream << "simpleFunction2 线程：id=" << this_thread::get_id() << "; " << number << name << endl;
    cout << msgStream.str();
  }
}

void downloadFileFunction(string url, int start, int end)
{
  stringstream msg;
  msg << "线程：" << this_thread::get_id() << "正在从 " << url << " 下载文件，下载段为：" << start << "到" << end << endl;
  cout << msg.str();

  this_thread::sleep_for(chrono::milliseconds((end - start) / 2));

  msg.str("");
  msg << "线程：" << this_thread::get_id() << "已经完成下载，下载段为：" << start << "到" << end << endl;
  cout << msg.str();
}

void downloadFileDemo()
{

  thread downloadThread1(downloadFileFunction, "http:***.com/秘籍", 0, 2000);
  thread downloadThread2(downloadFileFunction, "http:***.com/秘籍", 2000, 3000);

  downloadThread1.join();
  downloadThread2.join();

  cout << "秘籍下载完毕开始修炼" << endl;

  // 在上面示例程序中最核心的处理是在主线程输出修炼之前通过子线程对象调用了 join() 方法，这样就能够保证两个子线程的任务都执行完毕了，也就是文件内容已经全部下载完成，
  // 主线程再对文件进行后续处理，如果子线程的文件没有下载完毕，主线程就去处理文件，很显然从逻辑上讲是有问题的。
}